import { ab2hex, promisify, isIOS } from "../libs/util";
import Peripheral from "../Peripheral";
import phera from "../const/phera";
import { request, checkError } from "../libs/req";
import { MEASURE_STATUS, deviceSku } from "./const";
import { mapi } from "../libs/api";
import { getLogger } from "../libs/logger";
import "./scale/QN";

export { MEASURE_STATUS };

const st = MEASURE_STATUS;

const logger = getLogger('FatScale');

/**
 * 体脂秤
 * @extends {Peripheral}
 */
class FatScale extends Peripheral {

  /**
   * */

  constructor() {
    super();
  }

  /**
   * 启动插件
   * @returns {Promise<statusInfo>} 启动状态
   */
  async start() {
    this.notifyBluetoothAdapterStateChange();
    this.notifyConnectionStateChange();
    logger.log('[start]');
    const stateRes = await getBluetoothAdapter();
    let { discovering, available } = stateRes;
    if (!available) {
      return Promise.reject({
        errCode: 10001,
        errMsg: 'not available'
      });
    } else if (discovering === false) {
      return promisify(phera.openBluetoothAdapter)();
    } else {
      // return Promise.reject(failmsg);
    }
    // todo discovery
  }


  notifyBluetoothAdapterStateChange() {
    phera.onBluetoothAdapterStateChange(res => {
      logger.log('[notifyBluetoothAdapterStateChange]', res);
      let { available, discovering } = res;
      this.available = available;
      this.discovering = discovering;

      if (available) {
        this.notifyDataChage({ code: st.PAUSE });
        logger.log('蓝牙已打开');
      } else {
        this.currentDevice = null;
        this.notifyDataChage({ code: st.NO_CONNECTION });
        // 
        logger.log('蓝牙已关闭');
      }
    });
  }

  notifyConnectionStateChange() {
    phera.onBLEConnectionStateChange(res => {
      logger.log('[onBLEConnectionStateChange]', res);
      const { deviceId, connected } = res;
      this.connected = connected;
      if (!connected) {
        this.notifyDataChage({ code: st.NO_CONNECTION });
        logger.log('设备已断开');
      } else if (this.currentDevice && deviceId === this.currentDevice.deviceId) {
        this.notifyDataChage({ code: st.PAUSE });
        logger.log('设备已重连');
      }
    });
  }

  /**
   * 创建连接并监听特征值
   * @private
   */
  async createConnect() {
    const { deviceId } = this.currentDevice;
    logger.log('[createConnect]', this.currentDevice);

    this.notifyDataChage({ code: st.CONNECTING });

    const connectRes = await this.connect(deviceId);
    logger.log('[createConnect#createBLEConnection]', connectRes);

    const servicesRes = await promisify(phera.getBLEDeviceServices)({ deviceId });
    logger.log('[createConnect#getBLEDeviceServices]\n', deviceId, servicesRes);

    const currentServices = this.getCurrentServices(servicesRes);
    const serviceId = currentServices.serviceId;
    const characterRes = await promisify(phera.getBLEDeviceCharacteristics)({ deviceId, serviceId });
    logger.log('[createConnect#getBLEDeviceCharacteristics]', characterRes);
    // todo: getBLEDeviceCharacteristics

    this.notifyCharacteristicValueChange();

    return promisify(phera.notifyBLECharacteristicValueChange)({
      state: true, // 启用 notify 功能
      deviceId,
      // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
      serviceId: currentServices.serviceId,
      // 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
      characteristicId: currentServices.readCharacteristicId
    });
  }

  getCurrentServices(servicesRes) {
    // TODO: clean
    return isIOS() ? QN.getIOSServices(servicesRes.services) : QN.getServices(servicesRes.services);
  }

  /**
   * 发现设备
   * @private
   */
  notifyDeviceFound() {
    phera.onBluetoothDeviceFound(async (res) => {
      logger.log('[onBluetoothDeviceFound]', res);

      const devices = res.devices;
      if (!Array.isArray(devices)) return;

      for (let deviceItem of devices) {
        const { RSSI, advertisData, deviceId } = deviceItem;
        const localName = deviceItem.localName || deviceItem.name;
        // if (localName.indexOf("QN-") > -1) debugger;

        let deviceObj = {
          deviceId,
          localName,
          RSSI,
          advertisData: ab2hex(advertisData) //转成16进制字符
        };
        const device = QN.getDevice(deviceObj); //获取蓝牙设备

        if (device) {
          logger.log(`[QN-Scale]`, device);
          //如果是广播秤
          if (device.isBroadcast) {
            if (this.currentDevice && device.mac !== this.currentDevice.mac) return;
            this.currentDevice = device;
            this.onBroadcastScale(device); //广播秤处理逻辑
          } else { //蓝牙秤
            this.currentDevice = device;
            //扫到设备后停止扫描
            await this.stopDiscovery();
            const res = await this.createConnect();
            logger.log('notifyDeviceFound#createConnect', res);
          }
          break;
        }
      }

    });
  }

  /**
   * 监听特征值变化
   * @private
   */
  notifyCharacteristicValueChange() {
    /* if (this._initedNotifyCharacterist) {
      return;
    }
    this._initedNotifyCharacterist = true; */

    phera.onBLECharacteristicValueChange(async ({ deviceId, value }) => {
      // logger.log(`[notifyCharacteristicValueChange#Change]`, deviceId, value);
      //转成16进制字符
      value = ab2hex(value);
      //根据特征值变化，获取对应的返回结果
      let result = QN.cmd({
        device: this.currentDevice,
        value
      });
      if (!result) return;

      const { code, weight, encryptStr } = result;
      const personInfo = this.getPerson();

      if (code == 3001) { //测量中
        logger.log('[3001]', weight, encryptStr);
        this.notifyDataChage({ code: st.MEASURING, weight, encryptStr });
      } else if (code == 3002 && this._notifyCode === 3001) { //心率秤首次返回的命令
        this.notifyDataChage({ code: st.CALC_PHYSICAL, weight, encryptStr });

        const { gender } = personInfo;
        this.decodeReport(encryptStr, personInfo, deviceSku).then(
          reportData => {
            const { reportDetail } = reportData;
            this.notifyDataChage({ code: st.REPORT_MID, weight, reportData });

            const { measurement } = JSON.parse(reportDetail);
            const { bodyfat, bmi } = this.achiveReport(measurement);

            // todo: 解码数据
            result.cmdString = QN.itemCmdString({
              gender, //性别
              bodyfat, //体脂率，类型值为3
              bmi //bmi，类型值2
            });
          }, () => {
            this.notifyDataChage({ code: st.PAUSE });
          }
        );
      }

      if (result.cmdString) { //向蓝牙秤发送命令
        if (code == 3003 && [st.REPORT, st.PAUSE].indexOf(this._notifyCode) === -1) {
          //拿到对应的encryptStr加密字符
          logger.log('测量完成', encryptStr);

          this.decodeReport(encryptStr, personInfo, deviceSku).then(
            reportData => {
              this.notifyDataChage({ code: st.REPORT, weight, reportData });
            }, () => {
              this.notifyDataChage({ code: st.PAUSE });
            }
          );

        }
        const ab = new ArrayBuffer(result.cmdString.length);
        const dv = new DataView(ab);

        result.cmdString.forEach((value, index) => {
          dv.setUint8(index, value);
        });

        phera.writeBLECharacteristicValue({
          deviceId,
          serviceId: result.serviceId,
          characteristicId: result.writeCharacteristicId,
          value: ab,
          success: () => {
            logger.log('写入成功');
          }
        });
      }

    });
  }

  notifyDataChage(args) {
    const { code } = args;
    this._notifyCode = code;
    this.getEvents('__onDataChange').forEach(cb => cb(args));
  }

  async onBroadcastScale(device) {
    let { code, encryptStr, weight } = device;
    logger.log('[onBroadcastScale]', code, weight, encryptStr);
    if (code == 3001) { //测量中
      this.notifyDataChage({ code: st.MEASURING, weight, encryptStr });
    } else if (code == 3003 && this._notifyCode < 3003) { //测量完成,得到encryptStr加密字符,并停止扫描
      logger.log('测量完成');
      this.notifyDataChage({ code: st.SCALE_READ, weight, encryptStr });
      //测量完成，停止扫描，使广播秤不再接收数据
      await this.stopDiscovery();

      this.notifyDataChage({ code: st.CALC_PHYSICAL, weight, encryptStr });

      this.decodeReport(encryptStr, this.getPerson(), deviceSku).then(
        reportData => {
          this.notifyDataChage({ code: st.REPORT, weight, reportData });
        }, () => {
          this.notifyDataChage({ code: st.PAUSE });
        });
    }
  }

  /**
   * 重启插件
   * @returns {Promise<statusInfo>} 启动状态
   */
  async restart() {
    await this.stop();
    this.start();
  }

  /**
   * 停用插件
   */
  async stop() {
    this.stoped = true;
    this.currentDevice = null;
    await this.stopDiscovery();
    await phera.closeBluetoothAdapter();
  }

  /**
   * 监听体脂秤状态变化
   * @public
   * @param {function(Object):void} cb 回调
   * 
   * @returns {function():void} 移除监听
   * 
   */
  onDataChange(cb) {
    return this.addEvent('__onDataChange', cb);
  }
  
  onDeviceChange(cb) {
    return this.addEvent('__onDeviceChange', cb);
  }

  /**
   * 解码测量数据
   * @public
   * @param {String} encryptStr 测量参数
   * @returns {Promise<MeasureResult>} 测量结果
   * 
   * #### 返回code
   * code,  report: {reportId, encryptStr, reportDetail}
   * | code  | desc  |
   * |-------|-------|
   * |   0   | 正常 |
   */
  decodeReport(encryptStr, personInfo, deviceSku) {
    const hexString = String(encryptStr).toUpperCase();
    const params = {
      _mt: 'healthIot.parseData',
      familyId: personInfo.presonId,
      height: parseInt(personInfo.height),
      birthDay: personInfo.birthday,
      gender: parseInt(personInfo.gender), // 
      originData: JSON.stringify({ hexString }),
      deviceSku
    };
    return new Promise((resolve, reject) => {
      request(mapi, params, 'POST', false).then(res => {
        let err;
        if (err = checkError(res)) {
          return reject(err);
        }
        const { content } = res;
        if (!content[0]) {
          return reject(reqfailmsg);
        }

        const { familyId, code, originData, parseResult } = content[0];
        const report = {
          code,
          presonId: familyId,
          originData,
          reportDetail: parseResult
        };
        resolve(report);
      }).catch(err => {
        logger.error('[decodeReport]', err);
        return Promise.reject(err);
      });
    });
  }

  /**
   * 测量结束
   * @public
   * @returns {Promise<Object>}
   */
  endMeasurement() {

  }

  /**
   * 设置personData
   * @param {fatScaleProps} props 初始化参数
   */
  async setPerson(personInfo) {
    this.curPerson = Object.assign({}, this.curPerson, personInfo);
  }

  getPerson() {
    return this.curPerson;
  }

  getConnectedDevice() {
    if (!this.currentDevice) return null;
    const { mac, isBroadcast } = this.currentDevice;
    return {
      deviceSku,
      mac,
      isBroadcast
    };
  }

  getDeviceSku() {
    return deviceSku;
  }

  achiveReport(measurementArr) {
    let obj = {};
    const p = {
      1: 'weight',
      2: 'bmi',
      3: 'bodyfat',
      4: 'subfat',
      5: 'visfat',
      6: 'water',
      7: 'muscle',
      8: 'bone',
      9: 'bmr',
      10: 'bodyShape',
      11: 'protein',
      12: 'lbm',
      13: 'muscleMass',
      14: 'bodyAge',
      15: 'score',
      16: 'heartRate',
      17: 'heartIndex'
    };
    measurementArr.forEach(function(item) {
      let name;
      if (name = p[item.type]) {
        obj[name] = item.value;
      }
    });
    return obj;
  }

}

export default FatScale;
